{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit DirectXLine;

interface

uses Classes, Colour, Direct3D9, AHMTypes;

type
  // Our custom vertex structure with untransformed (i.e. model space)
  // 3D coordinates and vertex colour
  PCustomVertex = ^TCustomVertex;
  TCustomVertex = packed record
    X, Y, Z: Single;
    Colour: TD3DColor;
  end;

  PTriangleVertices = ^TTriangleVertices;
  TTriangleVertices = packed array[0..2] of TCustomVertex;

  TAHMDXTriangleRenderer = class
  private
    FTriangles: TList;
    FCachedVertex: Boolean;
    FTriangleSize: Integer;
    FVertexSize: Integer;
    FVertexBuffer: IDirect3DVertexBuffer9;
    procedure AllocateVertexBuffer;
    procedure ClearTriangles;
  protected
    function CreateTriangle(Colour: TD3DColor): PTriangleVertices;
    procedure RenderTriangle(Triangle: PTriangleVertices);
    procedure FlushRenders;
    procedure ClearVertexCache;
    procedure OnLostDevice;
    procedure OnResetDevice;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TAHMDXLineManager = class
  private
    FTriangleRenderer: TAHMDXTriangleRenderer;
    FInitialised: Boolean;
    FCachedWorld: Boolean;
    FCachedOriginX: Single;
    FCachedOriginY: Single;
    F3DRotation: Boolean;
    F3DRotnMatrix: TD3DMatrix;
    F3DOriginX: Single;
    F3DOriginY: Single;
    FLineOriginX: Single;
    FLineOriginY: Single;
    FLineCached: Boolean;
  protected
    procedure DrawCircleArcW(X, Y, Radius, LineWidth, Angle, Sweep: Single; Colour: TD3DColor);
    procedure DrawEllipseArcW(X, Y, XRadius, YRadius, LineWidth, Angle, Sweep: Single; Colour: TD3DColor);
    procedure DrawCircleW(X, Y, Radius, Angle, Sweep: Single; Colour: TD3DColor);
    procedure DrawEllipseW(X, Y, XRadius, YRadius, Angle, Sweep: Single; Colour: TD3DColor);
    procedure DrawQuadW(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Single; Colour: TD3DColor);
    procedure TransformOrigin(var X, Y: Single);
    procedure TransformCoords(var DestRect: TAHMRectF; XScale, YScale: Single);
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetRotation(XOrigin, YOrigin: Single);
    procedure Set3DRotation(Rotation: TD3DMatrix);
    procedure Set3DTranslation(XOrigin, YOrigin: Single);
    procedure ResetRotation;
    procedure Reset3DRotation;
    procedure DrawCircle(X, Y, Radius, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
    procedure DrawEllipse(X, Y, XRadius, YRadius, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
    procedure DrawArc(X, Y, XRadius, YRadius, StartAngle, SweepAngle, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
    procedure FillCircle(X, Y, Radius: Single; Colour, Gamma: TAHMCanvasColour);
    procedure FillEllipse(X, Y, XRadius, YRadius: Single; Colour, Gamma: TAHMCanvasColour);
    procedure DrawFrame(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour; LineWidth, XScale, YScale: Single);
    procedure DrawRoundFrame(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour; LineWidth, CornerRadius, XScale, YScale: Single);
    procedure DrawRoundRect(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour; CornerRadius, XScale, YScale: Single);
    procedure DrawLine(X1, Y1, X2, Y2: Single; Colour, Gamma: TAHMCanvasColour; LineWidth: Single);
    procedure MoveLineTo(X, Y: Single);
    procedure DrawLineTo(X, Y: Single; Colour, Gamma: TAHMCanvasColour; LineWidth: Single);
    procedure OnLostDevice;
    procedure OnResetDevice;
    procedure FlushTriangles;
    procedure ReleaseResources;
    procedure BeginDrawing;
    procedure EndDrawing;
  end;

implementation

uses Windows, Math, D3DX9, DirectXHelper, Canvas, Screen;

const
  TRIANGLE_CACHE_SIZE = 100; // Maximum triangle render batch size

  // Our custom FVF, which describes our vertex structure (TCustomVertex)
  D3DFVF_CUSTOMVERTEX = (D3DFVF_XYZ or D3DFVF_DIFFUSE);

  // Initialise three vertices for rendering a triangle
  EMPTY_TRIANGLE: TTriangleVertices = (
    (X: -1.0; Y:  1.0; Z: 0.0; Colour: $ffffffff),
    (X: -1.0; Y: -1.0; Z: 0.0; Colour: $ffffffff),
    (X:  1.0; Y: -1.0; Z: 0.0; Colour: $ffffffff)
  );


constructor TAHMDXTriangleRenderer.Create;
begin
  inherited;

  FTriangles := TList.Create;

  // Initialise size of our vertex and triangle structures
  FVertexSize := SizeOf(TCustomVertex);
  FTriangleSize := SizeOf(TTriangleVertices);
end;

destructor TAHMDXTriangleRenderer.Destroy;
begin
  FTriangles.Free;

  inherited;
end;

procedure TAHMDXTriangleRenderer.AllocateVertexBuffer;
var
  hr: HRESULT;
begin
  // Create our DirectX vertex buffer for rendering shaded triangles. Here we
  // allocate sufficient memory to store 3 vertices (for a quad) and specify
  // dynamic usage since we'll need to amend vertex coordinates. Dynamic
  // vertex buffer must reside in default memory pool, hence we must release
  // and recreate it for lost device scenarios. Note we specify our custom
  // FVF flags which determines the format of the buffer contents.
  hr := DirectXInstance.DisplayDevice.CreateVertexBuffer(
          TRIANGLE_CACHE_SIZE * FTriangleSize, D3DUSAGE_DYNAMIC or D3DUSAGE_WRITEONLY,
          D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, FVertexBuffer, nil);

  if hr <> S_OK then
    DirectXInstance.RaiseError('Failed to create DirectX Line Vertex buffer', hr);
end;

procedure TAHMDXTriangleRenderer.ClearTriangles;
var
  i: Integer;
  Triangle: PTriangleVertices;
begin
  // Free all of our triangle vertices
  for i := 0 to Pred(FTriangles.Count) do
  begin
    Triangle := PTriangleVertices(FTriangles[i]);
    Dispose(Triangle);
  end;

  // Clear list contents
  FTriangles.Clear;
end;

function TAHMDXTriangleRenderer.CreateTriangle(Colour: TD3DColor): PTriangleVertices;
begin
  // Create new triangle record & initialise
  New(Result);
  CopyMemory(Result, @EMPTY_TRIANGLE, FTriangleSize);

  // Populate colours
  Result[0].Colour := Colour;
  Result[1].Colour := Colour;
  Result[2].Colour := Colour;
end;

procedure TAHMDXTriangleRenderer.RenderTriangle(Triangle: PTriangleVertices);
begin
  // If our buffer is full then flush buffer
  if FTriangles.Count = TRIANGLE_CACHE_SIZE then FlushRenders;

  // Add this triangle to our render list... we'll render in batches
  FTriangles.Add(Triangle);
end;

procedure TAHMDXTriangleRenderer.FlushRenders;
var
  i: Integer;
  hr: HRESULT;
  pVertices: PTriangleVertices;
begin
  // Do we have any outstanding triangles to submit for rendering?
  if FTriangles.Count > 0 then
  begin
    // Now we can lock and populate our vertex buffer. We specify discard
    // flag so that adapter can work on a parallel copy of the vertex buffer
    hr := FVertexBuffer.Lock(0, FTriangles.Count * FTriangleSize, Pointer(pVertices), D3DLOCK_DISCARD);
    if hr = D3D_OK then
    try
      // Populate all of our triangle vertices
      for i := 0 to Pred(FTriangles.Count) do
      begin
        CopyMemory(pVertices, FTriangles[i], FTriangleSize);
        Inc(pVertices);
      end;
    finally
      // Don't forget to unlock or we stall the adapter
      FVertexBuffer.Unlock;
    end;

    // Draw the triangles in the vertex buffer. This is broken into a few
    // steps. We are passing the vertices down a "stream", so first we need
    // to specify the source of that stream, which is our vertex buffer. Then
    // we need to let D3D know what vertex shader to use. Full, custom vertex
    // shaders are an advanced topic, but in most cases the vertex shader is
    // just the FVF, so that D3D knows what type of vertices we are dealing
    // with. Finally, we call DrawPrimitive() which does the actual rendering
    // of our geometry (in this case, just one triangle)
    if not FCachedVertex then
    begin
      DirectXInstance.DisplayDevice.SetStreamSource(0, FVertexBuffer, 0, FVertexSize);
      DirectXInstance.DisplayDevice.SetFVF(D3DFVF_CUSTOMVERTEX);
      DirectXInstance.DisplayDevice.SetTexture(0, nil);
      FCachedVertex := True;
    end;

    DirectXInstance.DisplayDevice.DrawPrimitive(D3DPT_TRIANGLELIST, 0, FTriangles.Count);

    // Now clear our cache
    ClearTriangles;
  end;
end;

procedure TAHMDXTriangleRenderer.ClearVertexCache;
begin
  // Clear vertex cache
  FCachedVertex := False;
end;

procedure TAHMDXTriangleRenderer.OnLostDevice;
begin
  // Abandon any pending triangle renders
  ClearTriangles;

  // Also need to release our vertex buffer
  FVertexBuffer := nil;
  FCachedVertex := False;
end;

procedure TAHMDXTriangleRenderer.OnResetDevice;
begin
  // Create vertex buffer for use with triangle renderer
  AllocateVertexBuffer;
end;

//----------------------------------------------------------------------------//

constructor TAHMDXLineManager.Create;
begin
  inherited;

  FTriangleRenderer := TAHMDXTriangleRenderer.Create;
end;

destructor TAHMDXLineManager.Destroy;
begin
  FTriangleRenderer.Free;

  inherited Destroy;
end;

procedure TAHMDXLineManager.SetRotation(XOrigin, YOrigin: Single);
begin
  // Flush any outstanding triangles before changing rotation origin
  FlushTriangles;
  FCachedWorld := True;
  FCachedOriginX := XOrigin;
  FCachedOriginY := YOrigin;
end;

procedure TAHMDXLineManager.Set3DRotation(Rotation: TD3DMatrix);
begin
  if not F3DRotation then
  begin
    // Flush any outstanding triangles before switching to 3D rotation mode
    FlushTriangles;
    F3DRotation := True;

    // Setup default identity translation
    F3DOriginX := 0.0;
    F3DOriginY := 0.0;
  end;

  // Cache 3D rotation matrix
  F3DRotnMatrix := Rotation;
end;

procedure TAHMDXLineManager.Set3DTranslation(XOrigin, YOrigin: Single);
begin
  if not F3DRotation then
  begin
    // Flush any outstanding triangles before switching to 3D rotation mode
    FlushTriangles;
    F3DRotation := True;

    // Setup default identity rotation matrix
    D3DXMatrixIdentity(F3DRotnMatrix);
  end;

  // Cache 3D translation properties
  F3DOriginX := XOrigin;
  F3DOriginY := YOrigin;
end;

procedure TAHMDXLineManager.ResetRotation;
begin
  // Flush outstanding triangles and reset our world transformation
  FlushTriangles;
  FCachedWorld := False;
end;

procedure TAHMDXLineManager.Reset3DRotation;
begin
  // Flush outstanding triangles and reset 3D rotation mode
  FlushTriangles;
  F3DRotation := False;
end;

procedure TAHMDXLineManager.DrawCircleArcW(X, Y, Radius, LineWidth, Angle, Sweep: Single; Colour: TD3DColor);
begin
  // Delegate to method below with consistent radius
  DrawEllipseArcW(X, Y, Radius, Radius, LineWidth, Angle, Sweep, Colour);
end;

procedure TAHMDXLineManager.DrawEllipseArcW(X, Y, XRadius, YRadius, LineWidth, Angle, Sweep: Single; Colour: TD3DColor);
var
  SinAngle, CosAngle: Extended;
  InnerX, InnerY, OuterX, OuterY: Single;
  X1, Y1, X2, Y2, DX1, DY1, DX2, DY2: Single;
  i, Steps: Integer;
begin
  // Compute inner and outer radius
  InnerX := XRadius - LineWidth;
  InnerY := YRadius - LineWidth;
  OuterX := XRadius + LineWidth;
  OuterY := YRadius + LineWidth;

  // Compute number of segments for circle approximation
  Steps := Round(OuterX * Sweep * 500);
  if Steps = 0 then Exit;

  // Compute offsets for start angle
  SinCos(Angle, SinAngle, CosAngle);
  DX1 := X - (InnerX * SinAngle);
  DY1 := Y + (InnerY * CosAngle);
  DX2 := X - (OuterX * SinAngle);
  DY2 := Y + (OuterY * CosAngle);

  // Calculate segments along sweep
  for i := 1 to Steps do
  begin
    // Start angle (start from previous segment)
    X1 := DX1; Y1 := DY1;
    X2 := DX2; Y2 := DY2;

    // Finish angle
    Angle := Angle + (Sweep / Steps);
    SinCos(Angle, SinAngle, CosAngle);
    DX1 := X - (InnerX * SinAngle);
    DY1 := Y + (InnerY * CosAngle);
    DX2 := X - (OuterX * SinAngle);
    DY2 := Y + (OuterY * CosAngle);

    // Submit quad to rendering batch
    DrawQuadW(X1, Y1, X2, Y2, DX2, DY2, DX1, DY1, Colour);
  end;
end;

procedure TAHMDXLineManager.DrawCircleW(X, Y, Radius, Angle, Sweep: Single; Colour: TD3DColor);
begin
  // Delegate to method below with consistent radius
  DrawEllipseW(X, Y, Radius, Radius, Angle, Sweep, Colour);
end;

procedure TAHMDXLineManager.DrawEllipseW(X, Y, XRadius, YRadius, Angle, Sweep: Single; Colour: TD3DColor);
var
  SinAngle, CosAngle: Extended;
  DX, DY: Single;
  i, Steps: Integer;
  Triangle: PTriangleVertices;
begin
  // Compute number of segments for circle approximation
  Steps := Round(XRadius * Sweep * 500);
  if Steps = 0 then Exit;

  // Compute offsets for start angle
  SinCos(Angle, SinAngle, CosAngle);
  DX := X - (XRadius * SinAngle);
  DY := Y + (YRadius * CosAngle);

  // Calculate segments along sweep
  for i := 1 to Steps do
  begin
    // Allocate a new triangle rendering instance
    Triangle := FTriangleRenderer.CreateTriangle(Colour);

    // Origin - same for all segments
    Triangle[0].X := X;
    Triangle[0].Y := Y;

    // Start angle
    Triangle[1].X := DX;
    Triangle[1].Y := DY;

    // Finish angle
    Angle := Angle + (Sweep / Steps);
    SinCos(Angle, SinAngle, CosAngle);
    DX := X - (XRadius * SinAngle);
    DY := Y + (YRadius * CosAngle);
    Triangle[2].X := DX; // save for next segment
    Triangle[2].Y := DY; // save for next segment

    // Submit triangle to rendering batch
    FTriangleRenderer.RenderTriangle(Triangle);
  end;
end;

procedure TAHMDXLineManager.DrawQuadW(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Single; Colour: TD3DColor);
var
  Triangle, Triangle2: PTriangleVertices;
begin
  // Create two triangles for rendering quad
  Triangle := FTriangleRenderer.CreateTriangle(Colour);
  Triangle2 := FTriangleRenderer.CreateTriangle(Colour);

  // Populate vertex coordinates using a clockwise winding order
  Triangle[0].X := X1;
  Triangle[0].Y := Y1;
  Triangle[1].X := X2;
  Triangle[1].Y := Y2;
  Triangle[2].X := X3;
  Triangle[2].Y := Y3;
  Triangle2[0].X := X1;
  Triangle2[0].Y := Y1;
  Triangle2[1].X := X3;
  Triangle2[1].Y := Y3;
  Triangle2[2].X := X4;
  Triangle2[2].Y := Y4;

  // Now submit our triangles to the rendering batch
  FTriangleRenderer.RenderTriangle(Triangle);
  FTriangleRenderer.RenderTriangle(Triangle2);
end;

procedure TAHMDXLineManager.TransformOrigin(var X, Y: Single);
var
  matTrans, matWorld: TD3DMatrix;
begin
  // Calculate transformed world space coordinates for our circle
  X := 1 - (X * 2);
  Y := 1 - (Y * 2);

  // Are we using full 3D rotation mode for rendering?
  if F3DRotation then
  begin
    // Build a transformation matrix to position rotation origin
    D3DXMatrixTranslation(matTrans, F3DOriginX, F3DOriginY, 0.0);

    // Combine translation and rotation matrix to perform 3D rotation
    D3DXMatrixMultiply(matWorld, matTrans, F3DRotnMatrix);

    // Apply inverse transformation matrix to restore rotation origin
    D3DXMatrixTranslation(matTrans, -F3DOriginX, -F3DOriginY, 0.0);
    D3DXMatrixMultiply(matWorld, matWorld, matTrans);

    // Apply new world matrix to DirectX device
    DirectXInstance.DisplayDevice.SetTransform(D3DTS_WORLD, matWorld);
  end
  else if FCachedWorld then
  begin
    // Correct origin if we're using a world transformation for rotation
    X := X - FCachedOriginX;
    Y := Y - FCachedOriginY;
  end;
end;

procedure TAHMDXLineManager.TransformCoords(var DestRect: TAHMRectF; XScale, YScale: Single);
var
  matTrans, matScale, matWorld: TD3DMatrix;
begin
  // Calculate transformed world space coordinates for our frame
  DestRect.X := 1 - ((DestRect.X * 2) + DestRect.Width);
  DestRect.Y := 1 - ((DestRect.Y * 2) + DestRect.Height);

  // Are we using full 3D rotation mode for rendering?
  if F3DRotation then
  begin
    // Build a scaling matrix to scale model to world coordinates
    D3DXMatrixScaling(matScale, XScale, YScale, 1.0);

    // Build a transformation matrix to position rotation origin
    D3DXMatrixTranslation(matTrans, F3DOriginX, F3DOriginY, 0.0);

    // Combine scale and rotation matrix to perform scaling
    D3DXMatrixMultiply(matTrans, matTrans, matScale);

    // Combine translation and rotation matrix to perform 3D rotation
    D3DXMatrixMultiply(matWorld, matTrans, F3DRotnMatrix);

    // Apply inverse transformation matrix to restore rotation origin
    D3DXMatrixTranslation(matTrans, -F3DOriginX, -F3DOriginY, 0.0);
    D3DXMatrixMultiply(matWorld, matWorld, matTrans);

    // Apply new world matrix to DirectX device
    DirectXInstance.DisplayDevice.SetTransform(D3DTS_WORLD, matWorld);
  end
  else
  begin
    // Correct origin if we're using a world transformation for rotation
    if FCachedWorld then
    begin
      DestRect.X := DestRect.X - FCachedOriginX;
      DestRect.Y := DestRect.Y - FCachedOriginY;
    end;

    // We don't use scaling transform so width & height always set in world space
    DestRect.Width := DestRect.Width * XScale;
    DestRect.Height := DestRect.Height * YScale;
  end;
end;

procedure TAHMDXLineManager.DrawCircle(X, Y, Radius, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
var
  DXColour: DWORD;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space origin for our circle
  TransformOrigin(X, Y);

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Draw circle
  DrawCircleArcW(X, Y, Radius, LineWidth, 0, Pi * 2, DXColour);
end;

procedure TAHMDXLineManager.DrawEllipse(X, Y, XRadius, YRadius, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
var
  DXColour: DWORD;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space origin for our ellipse
  TransformOrigin(X, Y);

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Draw ellipse
  DrawEllipseArcW(X, Y, XRadius, YRadius, LineWidth, 0, Pi * 2, DXColour);
end;

procedure TAHMDXLineManager.DrawArc(X, Y, XRadius, YRadius, StartAngle, SweepAngle, LineWidth: Single; Colour, Gamma: TAHMCanvasColour);
var
  DXColour: DWORD;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space origin for our ellipse
  TransformOrigin(X, Y);

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Draw arc
  DrawEllipseArcW(X, Y, XRadius, YRadius, LineWidth, StartAngle, SweepAngle, DXColour);
end;

procedure TAHMDXLineManager.FillCircle(X, Y, Radius: Single; Colour, Gamma: TAHMCanvasColour);
var
  DXColour: DWORD;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space origin for our circle
  TransformOrigin(X, Y);

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Draw circle
  DrawCircleW(X, Y, Radius, 0, Pi * 2, DXColour);
end;

procedure TAHMDXLineManager.FillEllipse(X, Y, XRadius, YRadius: Single; Colour, Gamma: TAHMCanvasColour);
var
  DXColour: DWORD;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space origin for our ellipse
  TransformOrigin(X, Y);

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Draw ellipse
  DrawEllipseW(X, Y, XRadius, YRadius, 0, Pi * 2, DXColour);
end;

procedure TAHMDXLineManager.DrawFrame(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour;
                                      LineWidth, XScale, YScale: Single);
var
  DXColour: DWORD;
  X1Min, X1Max, Y1Min, Y1Max, X2Min, X2Max, Y2Min, Y2Max: Single;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space coordinates for our frame
  TransformCoords(DestRect, XScale, YScale);

  // If not using scaling transform then line width needs to be scaled
  if not F3DRotation then LineWidth := LineWidth * XScale;

  // Calculate world coordinates for each inner and outer corners
  X1Min := DestRect.X - DestRect.Width - LineWidth;
  Y1Min := DestRect.Y - DestRect.Height - LineWidth;
  X2Min := DestRect.X + DestRect.Width - LineWidth;
  Y2Min := DestRect.Y + DestRect.Height - LineWidth;
  X1Max := DestRect.X - DestRect.Width + LineWidth;
  Y1Max := DestRect.Y - DestRect.Height + LineWidth;
  X2Max := DestRect.X + DestRect.Width + LineWidth;
  Y2Max := DestRect.Y + DestRect.Height + LineWidth;

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Frame is composed of four separate quads
  DrawQuadW(X1Min, Y1Min, X2Min, Y1Min, X2Min, Y1Max, X1Min, Y1Max, DXColour);
  DrawQuadW(X2Min, Y1Min, X2Max, Y1Min, X2Max, Y2Min, X2Min, Y2Min, DXColour);
  DrawQuadW(X1Max, Y2Min, X2Max, Y2Min, X2Max, Y2Max, X1Max, Y2Max, DXColour);
  DrawQuadW(X1Min, Y1Max, X1Max, Y1Max, X1Max, Y2Max, X1Min, Y2Max, DXColour);
end;

procedure TAHMDXLineManager.DrawRoundFrame(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour;
                                           LineWidth, CornerRadius, XScale, YScale: Single);
var
  DXColour: DWORD;
  X1Min, X1Max, X2Min, X2Max, X3Min, X3Max: Single;
  Y1Min, Y1Max, Y2Min, Y2Max, Y3Min, Y3Max: Single;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space coordinates for our frame
  TransformCoords(DestRect, XScale, YScale);

  // If not using scaling transform then scale line width & corner radius
  if not F3DRotation then
  begin
    LineWidth := LineWidth * XScale;
    CornerRadius := CornerRadius * XScale;
  end;

  // Clip corner radius to half of shortest side, otherwise we get odd effects
  if DestRect.Width < DestRect.Height then
  begin
    if CornerRadius > (DestRect.Width / 2) then CornerRadius := DestRect.Width / 2;
  end
  else
    if CornerRadius > (DestRect.Height / 2) then CornerRadius := DestRect.Height / 2;

  // Calculate world coordinates for each inner and outer corners
  X1Min := DestRect.X - DestRect.Width - LineWidth;
  Y1Min := DestRect.Y - DestRect.Height - LineWidth;
  X2Min := DestRect.X + DestRect.Width - LineWidth;
  Y2Min := DestRect.Y + DestRect.Height - LineWidth;
  X1Max := DestRect.X - DestRect.Width + LineWidth;
  Y1Max := DestRect.Y - DestRect.Height + LineWidth;
  X2Max := DestRect.X + DestRect.Width + LineWidth;
  Y2Max := DestRect.Y + DestRect.Height + LineWidth;
  X3Min := DestRect.X + DestRect.Width - CornerRadius;
  X3Max := DestRect.X - DestRect.Width + CornerRadius;
  Y3Min := DestRect.Y + DestRect.Height - CornerRadius;
  Y3Max := DestRect.Y - DestRect.Height + CornerRadius;

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Rectangle is composed of four separate quads with arcs for each corner
  DrawQuadW(X3Max, Y1Min, X3Min, Y1Min, X3Min, Y1Max, X3Max, Y1Max, DXColour);
  DrawCircleArcW(X3Min, Y3Max, CornerRadius, LineWidth, Pi, Pi/2, DXColour);
  DrawQuadW(X2Min, Y3Max, X2Max, Y3Max, X2Max, Y3Min, X2Min, Y3Min, DXColour);
  DrawCircleArcW(X3Min, Y3Min, CornerRadius, LineWidth, 3*Pi/2, Pi/2, DXColour);
  DrawQuadW(X3Max, Y2Min, X3Min, Y2Min, X3Min, Y2Max, X3Max, Y2Max, DXColour);
  DrawCircleArcW(X3Max, Y3Min, CornerRadius, LineWidth, 0, Pi/2, DXColour);
  DrawQuadW(X1Min, Y3Max, X1Max, Y3Max, X1Max, Y3Min, X1Min, Y3Min, DXColour);
  DrawCircleArcW(X3Max, Y3Max, CornerRadius, LineWidth, Pi/2, Pi/2, DXColour);
end;

procedure TAHMDXLineManager.DrawRoundRect(DestRect: TAHMRectF; Colour, Gamma: TAHMCanvasColour;
                                          CornerRadius, XScale, YScale: Single);
var
  DXColour: DWORD;
  X1Min, X1Max, Y1Min, Y1Max, X2Min, X2Max, Y2Min, Y2Max: Single;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space coordinates for our frame
  TransformCoords(DestRect, XScale, YScale);

  // If not using scaling transform then corner radius needs to be scaled
  if not F3DRotation then CornerRadius := CornerRadius * XScale;

  // Clip corner radius to half of shortest side, otherwise we get odd effects
  if DestRect.Width < DestRect.Height then
  begin
    if CornerRadius > (DestRect.Width / 2) then CornerRadius := DestRect.Width / 2;
  end
  else
    if CornerRadius > (DestRect.Height / 2) then CornerRadius := DestRect.Height / 2;

  // Calculate world coordinates for each inner and outer corners
  X1Min := DestRect.X - DestRect.Width;
  Y1Min := DestRect.Y - DestRect.Height;
  X2Min := DestRect.X + DestRect.Width - CornerRadius;
  Y2Min := DestRect.Y + DestRect.Height - CornerRadius;
  X1Max := DestRect.X - DestRect.Width + CornerRadius;
  Y1Max := DestRect.Y - DestRect.Height + CornerRadius;
  X2Max := DestRect.X + DestRect.Width;
  Y2Max := DestRect.Y + DestRect.Height;

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Round rectangle is composed of a central quad, four corner fans and
  // four quads (one along each side) to fill in the gaps
  DrawQuadW(X1Max, Y1Min, X2Min, Y1Min, X2Min, Y1Max, X1Max, Y1Max, DXColour);
  DrawCircleW(X2Min, Y1Max, CornerRadius, Pi, Pi/2, DXColour);
  DrawQuadW(X2Min, Y1Max, X2Max, Y1Max, X2Max, Y2Min, X2Min, Y2Min, DXColour);
  DrawCircleW(X2Min, Y2Min, CornerRadius, 3*Pi/2, Pi/2, DXColour);
  DrawQuadW(X1Max, Y2Min, X2Min, Y2Min, X2Min, Y2Max, X1Max, Y2Max, DXColour);
  DrawCircleW(X1Max, Y2Min, CornerRadius, 0, Pi/2, DXColour);
  DrawQuadW(X1Min, Y1Max, X1Max, Y1Max, X1Max, Y2Min, X1Min, Y2Min, DXColour);
  DrawCircleW(X1Max, Y1Max, CornerRadius, Pi/2, Pi/2, DXColour);
  DrawQuadW(X2Min, Y2Min, X1Max, Y2Min, X1Max, Y1Max, X2Min, Y1Max, DXColour);
end;

procedure TAHMDXLineManager.DrawLine(X1, Y1, X2, Y2: Single; Colour, Gamma: TAHMCanvasColour;
                                     LineWidth: Single);
begin
  // Delegate this to methods below
  MoveLineTo(X1, Y1);
  DrawLineTo(X2, Y2, Colour, Gamma, LineWidth);
end;

procedure TAHMDXLineManager.MoveLineTo(X, Y: Single);
begin
  // Just store origin for calculation later
  FLineOriginX := X;
  FLineOriginY := Y;
  FLineCached := False;
end;

procedure TAHMDXLineManager.DrawLineTo(X, Y: Single; Colour, Gamma: TAHMCanvasColour; LineWidth: Single);
var
  DXColour: DWORD;
  matTrans, matWorld: TD3DMatrix;
  Angle, DX, DY, X1, Y1, X2, Y2: Single;
begin
  // Skip rendering unless called within Begin/End setup pair
  if not FInitialised then Exit;

  // Calculate transformed world space coordinates for our line
  if not FLineCached then
  begin
    X1 := 1 - (FLineOriginX * 2);
    Y1 := 1 - (FLineOriginY * 2);
  end
  else
  begin
    // Use previous lines calculated endpoint in world space
    X1 := FLineOriginX;
    Y1 := FLineOriginY;
  end;
  X2 := 1 - (X * 2);
  Y2 := 1 - (Y * 2);

  // Are we using full 3D rotation mode for rendering?
  if F3DRotation then
  begin
    // Build a transformation matrix to position rotation origin
    D3DXMatrixTranslation(matTrans, F3DOriginX, F3DOriginY, 0.0);

    // Combine translation and rotation matrix to perform 3D rotation
    D3DXMatrixMultiply(matWorld, matTrans, F3DRotnMatrix);

    // Apply inverse transformation matrix to restore rotation origin
    D3DXMatrixTranslation(matTrans, -F3DOriginX, -F3DOriginY, 0.0);
    D3DXMatrixMultiply(matWorld, matWorld, matTrans);

    // Apply new world matrix to DirectX device
    DirectXInstance.DisplayDevice.SetTransform(D3DTS_WORLD, matWorld);
  end
  else if FCachedWorld then
  begin
    // Correct origin if we're using a world transformation for rotation
    if not FLineCached then
    begin
      X1 := X1 - FCachedOriginX;
      Y1 := Y1 - FCachedOriginY;
    end;
    X2 := X2 - FCachedOriginX;
    Y2 := Y2 - FCachedOriginY;
  end;

  // Use gamma corrected pen if set, or gamma if not, or plain white
  if Assigned(Colour) then DXColour := Colour.InternalGammaCorrect(Gamma)
  else if Assigned(Gamma) then DXColour := Gamma.InternalARGB
  else DXColour := D3DCOLOR_ARGB(255,255,255,255);

  // Compute normals for drawing line
  Angle := ArcTan2(Y2 - Y1, X2 - X1);
  DX := LineWidth * Sin(Angle);
  DY := LineWidth * Cos(Angle);

  // Calculate coordinates for four corners of centre quad
  DrawQuadW(X1-DX, Y1+DY, X2-DX, Y2+DY, X2+DX, Y2-DY, X1+DX, Y1-DY, DXColour);

  // Draw rounded ends of line
  DrawCircleW(X1, Y1, LineWidth, Angle, Pi, DXColour);
  DrawCircleW(X2, Y2, LineWidth, Angle + Pi, Pi, DXColour);

  // Cache line end points in case we want to draw a continuous line
  FLineOriginX := X2;
  FLineOriginY := Y2;
  FLineCached := True;
end;

procedure TAHMDXLineManager.OnLostDevice;
begin
  // Inform triangle renderer of lost device event
  FTriangleRenderer.OnLostDevice;

  // Reset our cached world transformation flag
  FCachedWorld := False;
  FInitialised := False;
end;

procedure TAHMDXLineManager.FlushTriangles;
begin
  // Flush any outstanding triangle render requests
  FTriangleRenderer.FlushRenders;
end;

procedure TAHMDXLineManager.OnResetDevice;
begin
  // Inform triangle renderer of reset device event
  FTriangleRenderer.OnResetDevice;
end;

procedure TAHMDXLineManager.ReleaseResources;
begin
  // Call OnLost device method to free resources
  OnLostDevice;
end;

procedure TAHMDXLineManager.BeginDrawing;
begin
  // Renderer will need to reset device state
  FTriangleRenderer.ClearVertexCache;
  FInitialised := True;
end;

procedure TAHMDXLineManager.EndDrawing;
begin
  // Flush any outstanding rendering & clear device state
  FlushTriangles;
  FInitialised := False;
end;

end.
